﻿using System.Collections.Generic;
using System.Reflection;
using SL5.Common;

namespace System.Windows.Markup
{
	using Expression = System.Linq.Expressions.Expression;

	/// <summary>
	/// Static extension.
	/// </summary>
	public sealed class StaticExtension : MarkupExtension
	{
		#region Private Static Variables 
		private static readonly Dictionary<Tuple<Type, string>, Func<object>> _functions = new Dictionary<Tuple<Type, string>, Func<object>>();
		#endregion Private Static Variables 

		#region Public Static Methods 
		/// <summary>
		/// Gets the function for the specified <paramref name="type"/> and <paramref name="memberName"/>.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="memberName">Name of the member.</param>
		/// <returns>Func.</returns>
		public static Func<object> GetFunction(Type type, string memberName)
		{
			Func<object> function;
			lock (StaticExtension._functions)
			{
				Tuple<Type, string> key = Tuple.Create(type, memberName);
				if (!StaticExtension._functions.TryGetValue(key, out function))
				{
					Expression body;
					if (type.IsEnum)
					{
						body = Expression.Constant(
							Enum.Parse(
								type,
								memberName,
								false
							),
							typeof(object)
						);
					}
					else
					{
						MemberInfo member = type.GetField(memberName, BindingFlags.Public | BindingFlags.Static) ??
							(MemberInfo)type.GetProperty(memberName, BindingFlags.Public | BindingFlags.Static);
						if (member == null)
						{
							throw new MissingMemberException(String.Concat(type.FullName, ".", memberName));
						}
						Expression memberExpression = Expression.MakeMemberAccess(null, member);
						body = !memberExpression.Type.IsValueType ? memberExpression : Expression.Convert(
							memberExpression,
							typeof(object)
						);
					}
					StaticExtension._functions.Add(key, function = Expression.Lambda<Func<object>>(
						body
					).Compile());
				}
			}
			return function;
		}
		#endregion Public Static Methods 

		#region Public Properties 
		/// <summary>
		/// Gets or sets the member.
		/// </summary>
		/// <value>The member.</value>
		public string Member
		{
			get;
			set;
		}
		#endregion Public Properties 

		#region Public Methods 
		/// <summary>
		/// When implemented in a derived class, returns an object that is provided as the value of the target property for this markup extension.
		/// </summary>
		/// <param name="serviceProvider">A service provider helper that can provide services for the markup extension.</param>
		/// <returns>The object value to set on the property where the extension is applied.</returns>
		public override object ProvideValue(IServiceProvider serviceProvider)
		{
			string[] tokens;
			if (String.IsNullOrEmpty(this.Member) || (tokens = this.Member.Split('.')).Length != 2)
			{
				throw new ArgumentException("Bad Member path");
			}
			IXamlTypeResolver typeResolver = serviceProvider.GetService<IXamlTypeResolver>();
			object value = StaticExtension.GetFunction(typeResolver.Resolve(tokens[0]), tokens[1]).Invoke();
			if (value != null)
			{
				IProvideValueTarget targetProvider = serviceProvider.GetService<IProvideValueTarget>();
				MemberInfo member;
				object convertedValue;
				if ((member = targetProvider.TargetProperty as MemberInfo) != null &&
					ConverterHelper.TryConvert(value, member.MemberType == MemberTypes.Property ? ((PropertyInfo)member).PropertyType : ((MethodInfo)member).ReturnType, null, out convertedValue))
				{
					return convertedValue;
				}
			}
			return value;
		}
		#endregion Public Methods 
	}
}